# -*- coding:utf-8 -*-
import os
import xml.etree.ElementTree as ET
from .dist import Exponential,Weibull,Uniform,LogNormal,Normal,Binomial, Fixed, Dirac,SoF
from .node import Node
from .erbd import ERBD
from .pms import PMS,Task
from .const_args import Dist

class Parser():
    """
        模型文件解析类。

        当前支持的文件格式是XML，可在此基础上扩展。
    """
    def __init__(self, xmlfile):
        self.xmlfile = xmlfile
        self.failure_models = {}
        self.repair_models={}
        self.tree = None
        self.root = self.get_root()

    def get_root(self):
        # 可接受模型文件和模型字符串
        if os.path.exists(self.xmlfile):
            self.tree = ET.parse(self.xmlfile)
            return self.tree.getroot()
        else:
            return ET.fromstring(self.xmlfile)
    
    def get_xml_str(self):
        return  ET.tostring(self.tree.getroot(), encoding='utf-8', method='xml').decode('utf-8')

    def parse_xml_ET(self):

        pms = PMS()

        for failure_model in self.root.find('failure_models').findall('failure_model'):
            fm_name = failure_model.attrib['name']
            fm_type = failure_model.attrib['type']
            argus = []
            if fm_name not in failure_model:                
                if fm_type==Dist.exp:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = Exponential(Dist.exp,argus)
                elif fm_type == Dist.weibull:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = Weibull(Dist.weibull,argus)
                elif fm_type == Dist.lognormal:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = LogNormal(Dist.lognormal,argus)
                elif fm_type == Dist.normal:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = Normal(Dist.normal,argus)
                elif fm_type == Dist.uniform:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = Uniform(Dist.uniform,argus)
                elif fm_type == Dist.bernoulli:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = Fixed(Dist.bernoulli,argus)
                elif fm_type == Dist.binomial:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = Binomial(Dist.binomial,argus)
                elif fm_type == Dist.sof:
                    for child in failure_model:
                        argus.append((float)(child.attrib['value']))
                    dist = SoF(Dist.sof,argus)
                #print(fm_name)

                self.failure_models[fm_name] = dist

        # 解析repair_models
        if self.root.find('repair_models') is not None:
            for repair_model in self.root.find('repair_models').findall('repair_model'):
                rm_name = repair_model.attrib['name']
                rm_type = repair_model.attrib['type']
                argus = []
                if rm_name not in self.repair_models: 
                    if rm_type==Dist.exp:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = Exponential(Dist.exp,argus)
                    elif rm_type == Dist.weibull:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = Weibull(Dist.weibull,argus)
                    elif rm_type == Dist.lognormal:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = LogNormal(Dist.lognormal,argus)
                    elif rm_type == Dist.normal:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = Normal(Dist.normal,argus)
                    elif rm_type == Dist.uniform:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = Uniform(Dist.uniform,argus)
                    elif rm_type == Dist.bernoulli:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = Fixed(Dist.ber,argus)
                    elif rm_type == Dist.binomial:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = Binomial(Dist.binomial,argus)
                    elif rm_type == Dist.dirac:
                        for child in repair_model:
                            argus.append((float)(child.attrib['value']))
                        dist = Dirac(Dist.dirac,argus)

                    self.repair_models[rm_name] = dist

    # 解析models
        models = {}
        for model in self.root.find('models').findall('model'):
            
            model_name = model.attrib['name']
            rbd = ERBD(model_name)
            all_nodes={}
            for node in model:
                top = self.handle_model(node,all_nodes)
            rbd.all_nodes = all_nodes
            rbd.entry = top
            
            models[model_name] = rbd
            pms.models[model_name] = rbd

        
        id=0
        for task in self.root.find('tasks').findall('task'):
            task_duration = float(task.attrib['duration'])
            model_name = task.attrib['model']
            tk = Task(id,'',task_duration,model_name)
            pms.tasks.append(tk)
            id+=1

        return pms
    
    def handle_model(self,model,all_nodes):
        ele = Node(model.attrib["name"])
        ele.node_type = model.attrib["type"]
        if model.tag == "logic":
            for cnode in model:
                child = self.handle_model(cnode,all_nodes)
                ele.children.append(child)
                child.parent = ele
        elif model.tag == "node":
            ele.status = 'on'
            for fm in model:
                if fm.tag=="failure_model":
                    ele.failure_dist = self.failure_models[fm.attrib["name"]]
                elif fm.tag=="repair_model":
                    ele.repair_dist = self.repair_models[fm.attrib["name"]]
        all_nodes[ele.name] = ele
        return ele
    
    def validate_xml(self):
        errorMsg=''
        try:
            if self.root.tag != 'des':
                errorMsg += f"Root element must be 'des' "
            # Validate <tasks>
            tasks = self.root.find('tasks')
            if tasks is None:
                errorMsg+= "Missing <tasks> element"
            else:
                for task in tasks.findall('task'):
                    if 'duration' not in task.attrib or 'model' not in task.attrib:
                        errorMsg+=f"Task missing 'duration' or 'model' attribute: {ET.tostring(task, encoding='unicode')}"
            # Validate <models>
            models = self.root.find('models')
            if models is None:
                errorMsg+="Missing <models> element"
            else:
                for model in models.findall('model'):
                    if 'name' not in model.attrib:
                        errorMsg+=f"Model missing 'name' attribute: {ET.tostring(model, encoding='unicode')}"

                    if not self.validate_logic(model):
                        return False

            # Validate <failure_models>
            failure_models = self.root.find('failure_models')
            if failure_models is None:
                errorMsg+="Missing <failure_models> element"
            else:    
                for failure_model in failure_models.findall('failure_model'):
                    if 'name' not in failure_model.attrib or 'type' not in failure_model.attrib:
                        errorMsg+=f"Failure model missing 'name' or 'type' attribute: {ET.tostring(failure_model, encoding='unicode')}"
                    #if not self.validate_failure_model(failure_model):
                    #    return False

            # Validate <repair_models>
            repair_models = self.root.find('repair_models')
            if repair_models:
                for repair_model in repair_models.findall('repair_model'):
                    if 'name' not in repair_model.attrib or 'type' not in repair_model.attrib:
                        errorMsg+=f"Repair model missing 'name' or 'type' attribute: {ET.tostring(repair_model, encoding='unicode')}"
                        return errorMsg
                    if not self.validate_repair_model(repair_model):
                        return errorMsg
            return errorMsg

        except ET.ParseError as e:
            errorMsg+=f"Error parsing XML file: {e}"
            return errorMsg

    def validate_logic(self,element):
        for logic in element.findall('logic'):
            if 'name' not in logic.attrib or 'type' not in logic.attrib:
                #print(f"Logic missing 'name' or 'type' attribute: {ET.tostring(logic, encoding='unicode')}")
                return False
            if not self.validate_logic(logic):
                return False

        for node in element.findall('node'):
            if 'name' not in node.attrib or 'type' not in node.attrib:
                #print(f"Node missing 'name' or 'type' attribute: {ET.tostring(node, encoding='unicode')}")
                return False
            if not self.validate_node(node):
                return False

        return True

    def validate_node(self,node):
        failure_model = node.find('failure_model')
        if failure_model is None or 'name' not in failure_model.attrib:
            #print(f"Node missing 'failure_model' or 'name' attribute: {ET.tostring(node, encoding='unicode')}")
            return False

        repair_model = node.find('repair_model')
        if repair_model is not None and 'name' not in repair_model.attrib:
            #print(f"Node repair model missing 'name' attribute: {ET.tostring(node, encoding='unicode')}")
            return False

        return True

    def validate_failure_model(self,failure_model):
        if failure_model.attrib['type'] == 'exponential':
            if failure_model.find('failure-rate') is None:
                #print(f"Exponential failure model missing 'failure-rate': {ET.tostring(failure_model, encoding='unicode')}")
                return False
        elif failure_model.attrib['type'] == 'fixed':
            if failure_model.find('reliability') is None:
                #print(f"Fixed failure model missing 'unreliability': {ET.tostring(failure_model, encoding='unicode')}")
                return False
        elif failure_model.attrib['type'] == 'weibull':
            if failure_model.find('scale') is None or failure_model.find('shape') is None:
                #print(f"Weibull failure model missing 'scale' or 'shape': {ET.tostring(failure_model, encoding='unicode')}")
                return False
        else:
            #print(f"Unknown failure model type: {failure_model.attrib['type']}")
            return False
        return True

    def validate_repair_model(self,repair_model):
        if repair_model.attrib['type'] == 'exponential':
            if repair_model.find('repair-rate') is None:
                #print(f"Exponential repair model missing 'repair-rate': {ET.tostring(repair_model, encoding='unicode')}")
                return False
        elif repair_model.attrib['type'] == 'weibull':
            if repair_model.find('scale') is None or repair_model.find('shape') is None:
                #print(f"Weibull repair model missing 'scale' or 'shape': {ET.tostring(repair_model, encoding='unicode')}")
                return False
        else:
            #print(f"Unknown repair model type: {repair_model.attrib['type']}")
            return False
        return True